package poker.logic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import poker.entities.Card;
import poker.entities.Force;
import poker.entities.Hand;
import poker.util.ConstantCardStrings;
import poker.util.MathUtils;
import poker.util.PokerCombinations;



public class HandsConfigManager {
	
	protected ArrayList<AbstractHandConfiguration> configs;
	private Hand rememberedBestHand;
	
	/*
	 * Poker rules according to:
	 * http://www.pokerlistings.com/poker-hand-ranking
 */
	public HandsConfigManager(){
		rememberedBestHand = null;
		configs = new ArrayList<AbstractHandConfiguration>();
		AbstractHandConfiguration config = new ConcreteHandConfiguration(PokerCombinations.HIGHEST_CARD,0);//0
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.ONE_PAIR,1);//1
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.TWO_PAIRS,2);//2
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.THREE_OF_A_KIND,3);//3
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.STRAIGHT,4);//4
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.FLUSH,5);//5
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.FULL_HOUSE,6);//6
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.FOUR_OF_A_KIND,7);//7
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.STRAIGHT_FLUSH,8);//8
		configs.add(config);
		config = new ConcreteHandConfiguration(PokerCombinations.ROYAL_FLUSH,9);//9
		configs.add(config);
		linkConfigs();
	}
	
	private void linkConfigs(){
		for(int index = 0; index < configs.size(); index++){
			if(index < configs.size() - 1)configs.get(index).setParentConfig(configs.get(index+1));
		}
	}
	
	/**
	 * 		getRememberedBestHand()
	 * @return The remembered best hand after the call of getBestHandConfiguration(ArrayL..)
	 */
	public Hand getRememberedBestHand(){
		return rememberedBestHand;
	}
	
	/**
	 * 		getBestHandConfiguration()
	 * @goal Regardless of hand currently being sorted or not, this algorithm looks for the best possible configuration
	 * 		 possible of the provided hand. This method sorts in descending order the provided hand.
	 * 
	 * 		 Algorithm goes as follows:
	 * 
	 * 			1. First check to see if cards all have the same symbol (filter).
	 * 				1.1 If it is the case -> basic configuration would be a flush then
	 * 					1.1.1 Check to see if it wouldn't be a straight flush
	 * 						1.1.1.1 If it is the case, if the first card is an ACE, then it is a royal flush otherwise we have to check
	 * 								for four of a kind and full house before determining that it is indeed a flush.
	 * 		    2. If symbols differ from card to card, check to see if card forces (card strengh) are all different.
	 * 				2.1 If it is the case, then it is the worst possible configuration, (Highest card configuration).
	 * 				2.2 Else, check to see if it is a four of a kind.
	 * 				2.3 If not, check for three of a kind
	 * 					2.3.1 if yes, also check for the presence of a double (full house) else three of a kind
	 * 				2.4 if not, count pairs
	 * 					2.4.1 count = 1 then configuration is ONE PAIR
	 * 					2..4.2 count >= 2 then configuration TWO PAIRS
	 * 					
	 * */
	public AbstractHandConfiguration getBestHandConfiguration(Hand hand){
		AbstractHandConfiguration config = null;
		if(hand == null || !(hand.hasValidCards())){
			return null;
		}
		hand.sortDescending();
		
		ArrayList<Card> cards = (ArrayList<Card>) hand.getCards().clone();
		
		boolean isFlush = false;
		boolean debug = true;
		for(int i = 1; i < cards.size(); i++){
			if(cards.get(0).getSymbol().toString().equals(cards.get(i).getSymbol().toString())){
				isFlush = true;
			}
			else{
				isFlush = false;break;
			}
		}
		
		if(isFlush){
			boolean isStraightFlush = false;
			/*Algorithm to check for straight combinations creates a copy of the current player's hand.
			 * In that copy, we get the first strongest card as a reference card (xfer) and remove the strongest card from
			 * the hand copy. Then we compare the force of the reference card to the next strongest card in the hand copy.
			 * Loop breaks as soon the next strongest card in hand isn't exactly equal to the current strongest card + 1.*/
			Card xfer;
			Hand tempHand = new Hand((ArrayList<Card>) cards.clone());
			for(int index = 0; tempHand.getCards().size() > 1; index++){
				xfer = tempHand.getStrongest();
				tempHand.removeCard(tempHand.getStrongest());
				
				if(tempHand.getStrongest().isValid() && 
						xfer.getForce().retrieve() == tempHand.getStrongest().getForce().retrieve()+1){
					isStraightFlush = true;
					
				}
				else{
					isStraightFlush = false;
					break;
				}
			}			
			if(isStraightFlush){
				config = configs.get(8);//STRAIGHT_FLUSH
				if(cards.get(0).getForce().retrieve() == ConstantCardStrings.ACE)config = configs.get(9);//ROYAL_FLUSH
			}
			else{
				HashMap<Integer,Integer> fCounter = new HashMap<Integer, Integer>();
				for(int i = 0; i < cards.size(); i++){
					if(fCounter.containsKey(cards.get(i).getForce().retrieve())){// if hash map already had key update count for it
						fCounter.put(cards.get(i).getForce().retrieve(),fCounter.get(cards.get(i).getForce().retrieve()) + 1);
					}
					else{//otherwise put in new key.
						fCounter.put(cards.get(i).getForce().retrieve(),1);
					}
				}//end mega for
				if(fCounter.containsValue(4)){ //CHECK FOUR OF A KIND just in case because four of a kind is better than a flush
					config = configs.get(7); //FOUR OF A KIND
				}
				else if(fCounter.containsValue(3) && fCounter.containsValue(2)){
					config = configs.get(6); //FULL HOUSE
				}
				else{ //Else indeed FLUSH
					config = configs.get(5);//FLUSH
				}
			}
		}
		else{
			boolean isHighest = true;
			for(int i = 0; i < cards.size(); i++){
				for(int j = 0; j < cards.size() && j != i; j++){
					if(cards.get(i).getForce().retrieve() == cards.get(j).getForce().retrieve()){
						isHighest = false;
						break;
					}
				}
			}
			if(isHighest){ // worst possible config = highest card.
				config = configs.get(0);
			}
			else{
				HashMap<Integer,Integer> fCounter = new HashMap<Integer, Integer>();
				for(int i = 0; i < cards.size(); i++){
					if(fCounter.containsKey(cards.get(i).getForce().retrieve())){// if hash map already had key update count for it
						fCounter.put(cards.get(i).getForce().retrieve(),fCounter.get(cards.get(i).getForce().retrieve()) + 1);
					}
					else{//otherwise put in new key.
						fCounter.put(cards.get(i).getForce().retrieve(),1);
					}
				}//end mega for
//				if(debug)System.out.println(fCounter.values());
				if(fCounter.containsValue(4)){
					config = configs.get(7); //FOUR OF A KIND
				}
				else if(fCounter.containsValue(3)){
					config = configs.get(3); //THREE OF A KIND
					if(fCounter.containsValue(2)){
						config = configs.get(6); //FULL HOUSE
					}
				}
				else{
					int pairsCount = 0;
					Object[] arr = fCounter.values().toArray();
					for(int i = 0; i < fCounter.values().toArray().length; i++){
						if(Integer.parseInt(arr[i].toString()) == 2){
							pairsCount += 1;
						}
					}
					if(pairsCount == 1){
						config = configs.get(1); //one pair
					}
					else if(pairsCount >= 2){
						config = configs.get(2);
					}
					else{ //shouldn't need this but included in case algorithm goes wrong.
						config = configs.get(0); // worst possible config = highest card.
					}
				}
			}
		}
		
		return config;
	}
	
	public AbstractHandConfiguration getBestHandConfiguration(ArrayList<Hand> hands){
		AbstractHandConfiguration config = null;
		int rememberedBestRank = 0;
		for(int i = 0; i < hands.size(); i++){
			if(i==0){
				config = getBestHandConfiguration(hands.get(i));
				rememberedBestRank = i;
			}else{
				if(config.getRank() != getBestHandConfiguration(hands.get(i)).getRank()){
					if(getBestHandConfiguration(hands.get(i)).getRank() > config.getRank()){
						config = getBestHandConfiguration(hands.get(i));
						rememberedBestRank = i;
					}
				}
			}
		}
		
		rememberedBestHand = hands.get(rememberedBestRank);
		rememberedBestHand.setCurrentHandConf(config);
		
		return config;
	}
	
	/**
	 * 		generateSubHands()
	 * @param mainHandCards , the main hand to generate sub hands from.
	 * @param subHandsSize , the sub hands maximum size in the returned hands array.
	 * @goal Only works for subgroups of 5 for now.
	 * @return Array of all possible sub hands of a specified hand. In other words,
	 * 			if a main hand of 7 cards is provided and the specified subHandsSize is 5,
	 * 			it will return an array of all possible sub hands of 5 cards from the main hand.
	 * 		   Returns null array if subHandsSize >= mainHand size. 
	 */
	public ArrayList<Hand> generateSubHands(ArrayList<Card> mainHandCards, int subHandsSize){
		ArrayList<Hand> allSubHands = null;
		boolean debug = false;
		/*
		 * According to MAT-350
		 * and with the help of:
		 * http://www.ehow.com/how_5142125_calculate-number-combinations.html
		 * Calculate the number of combinations of 5 cards possible out of 7 possibilities.
		 */
		double n = mainHandCards.size();
		double r = subHandsSize;
		double totalPossibleCombinations = 0;
		if(r >= n){
			return null;
		}
		else{
			totalPossibleCombinations = MathUtils.factor(n) / (MathUtils.factor(r) * (MathUtils.factor(n - r)));
		}
		
		allSubHands = new ArrayList<Hand>(); //Instantiate array containing all final hands.
		
		Hand handOf5; // temporary hand
		
		for(int iterator = 0; iterator < mainHandCards.size() - 1; iterator++){
			int init;
			init = iterator;
			int cutoffIndex = init + 1;
			while(cutoffIndex < mainHandCards.size()){
				handOf5 = new Hand();
				int search = 0, count = 0;
				boolean done = false;
				while(!done){
					
					if(!(search == init || search == cutoffIndex)){
						if(debug)System.out.println("Getting card at position ["+search+"] -> "+mainHandCards.get(search).toString());
						handOf5.addCard(mainHandCards.get(search));
						count++;
					}else{
						if(debug)System.out.println("Skipping");
					}
					if(count == subHandsSize)done = true;
					search++;				
				}
				allSubHands.add(handOf5);
				cutoffIndex++;
			}			
		}
		
//		@SuppressWarnings("unchecked")
//		ArrayList<Hand> clearedHands = new ArrayList<Hand>();// = (ArrayList<Hand>) allSubHands.clone();
//		clearedHands.addAll(allSubHands);
//		for(int ite = 0; ite < clearedHands.size(); ite++){
//			for(int j = 0; j < clearedHands.size(); j++){
//				if(j != ite){
//					if(clearedHands.get(ite).equals(clearedHands.get(j))){
//						clearedHands.remove(ite);
//					}
//				}
//			}
//		}
		
		if(debug)System.out.println();
		if(debug)System.out.println("Theorical possible number of combinations: "+totalPossibleCombinations);
		if(debug)System.out.println("Practical number of combinations obtained: "+allSubHands.size());
		if(debug)System.out.println();
		if(debug)System.out.println(mainHandCards.toString());
		if(debug)System.out.println();
		if(debug){
//			for(int i = 0; i < allSubHands.size(); i++){
//				System.out.println("#"+i+": "+allSubHands.get(i).toString());
//			}
//			System.out.println("--- cleared possibilities ---");
//			for(int i = 0; i < clearedHands.size(); i++){
//				System.out.println("#"+i+": "+clearedHands.get(i).toString());
//			}
		}
		
		return allSubHands;
		
	}
	
}
